
/* JSX translates XML literals like this
 *
 *   var jsx = <div foo="bar">some text</div>;
 *
 * into calls of "JSX driver function":
 *
 *  __jsx__("div", {foo:"bar"}, ["some text"]);
 *
 * note: 
 *  a) the call always have 3 arguments: string, object|null, array|null 
 *  b) __jsx__ can be redefined, e.g. for https://mithril.js.org it will be just
 *     
 *     __jsx__ = m; // using mithril as JSX driver 
 */

static __exception int next_web_token(JSParseState *s) {
  s->allow_web_name_token = 1;
  int r = next_token(s);
  s->allow_web_name_token = 0;
  return r;
}

static int is_non_space_run(const uint8_t* start, const uint8_t* end) {
  for (; start < end; ++start) if (!lre_is_space(*start)) break;
  for (; end > start; --end) if (!lre_is_space(*(end-1))) break;
  return (int)(end - start);
}

static int invalid_name_token(int t) {
  //return t != TOK_IDENT && !(t >= TOK_IF && t <= TOK_OF);
  return !token_is_ident(t);
}

static __exception int js_parse_jsx_text(JSParseState* s, const uint8_t* p, JSToken* token, const uint8_t** pp)
{
  int ret;
  uint32_t c;
  StringBuffer b_s, * b = &b_s;

  /* string */
  if (string_buffer_init(s->ctx, b, 32))
    goto fail;
  for (;;) {
    if (p >= s->buf_end)
      goto invalid_char;
    c = *p;
    if (c < 0x20) {
      if (c == '\r') {
        if (p[1] == '\n')
          p++;
        c = '\n';
      }
      if (c == '\n')
        s->line_num++;
    }
    p++;
    if (c == '{' || c == '<') {
      /* expr start */
      --p;
      break;
    }

    if (c == '&') { // parse HTML/XML entity
      char entity_buffer[256];
      char* pe = entity_buffer;
      int closed = 0;
      for (int n = 0; n < 255; ++n) {
        if (p >= s->buf_end)
          goto invalid_char;
        if (*p == ';') {
          closed = 1;
          ++p;
          break;
        }
        else if (*p == '{' || *p == '<') {
          /* expr start */
          *pe = '\0';
          goto ENTITY_FAILURE;
        }
        *pe++ = *p++;
      }
      *pe = '\0';

      if (entity_buffer[0] == '#') {
        char* pend = 0;
        if (entity_buffer[1] == "x" || entity_buffer[1] == "X") {
          long v = strtol(entity_buffer + 1, &pend, 16);
          if (pend && *pend == 0) {
            c = (uint32_t)v;
            goto TRANSLATED;
          }
        }
        else {
          char* pend = 0;
          long v = strtol(entity_buffer + 1, &pend, 10);
          if (pend && *pend == 0) {
            c = (uint32_t)v;
            goto TRANSLATED;
          }
        }
      }

      else if(s->ctx->rt->jsx_translate_entity) {
        const uint16_t* pc = s->ctx->rt->jsx_translate_entity(s->ctx, entity_buffer);
        if (pc) {
          for (;*pc;++pc) {
            if (string_buffer_putc(b, *pc))
              goto fail;
          }
          continue;
        }
      }

    ENTITY_FAILURE:
      if (string_buffer_putc(b, '&'))
        goto fail;
      for(char* t = entity_buffer; *t; ++t)
        if (string_buffer_putc(b, *t))
          goto fail;
      if (closed) {
        if (string_buffer_putc(b, ';'))
          goto fail;
      }
      continue;
    } else if (c >= 0x80) {
      const uint8_t* p_next;
      c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next);
      if (c > 0x10FFFF)
        goto invalid_utf8;
      p = p_next;
    } 
TRANSLATED:    
    if (string_buffer_putc(b, c))
      goto fail;
  }
  token->val = TOK_STRING;
  token->u.str.sep = c;
  token->u.str.str = string_buffer_end(b);
  *pp = p;
  return 0;

invalid_utf8:
  js_parse_error(s, "invalid UTF-8 sequence");
  goto fail;
invalid_char:
  js_parse_error(s, "unexpected end of string");
fail:
  string_buffer_free(b);
  return -1;
}



static int js_parse_jsx_expr(JSParseState *s, int level)
{
  int kids_count = 0;
  JSAtom  tag_atom = JS_ATOM_NULL;
  JSValue tag = JS_UNINITIALIZED;
  JSAtom  attr_name = JS_ATOM_NULL;
  JSValue attr_value = JS_UNINITIALIZED;
  JSValue translation_id = JS_UNINITIALIZED;
  int token_read = 0;
  int is_bodyless = 0;
  int is_fragment = 0;
  int translate = 0;

  const char* errmsg = "invalid JSX expression";
  char  msg_buffer[512] = { 0 };
#if defined(CONFIG_JSX_SCITER) // HTML shortcuts used by Sciter
  char class_buffer[512] = { 0 };
#endif

  JSRuntime* rt = JS_GetRuntime(s->ctx);

  // NOTE: caller already consumed '<' 
  if (next_web_token(s)) goto fail;

  if (s->token.val == '@') {
    translate = 1;
    if (next_web_token(s)) goto fail;
  }
  
  if (s->token.val == '>') {
    //empty tag, fragamet
    tag_atom = JS_DupAtom(s->ctx,JS_ATOM_empty_string);
    is_fragment = 1;
  }
  else if (invalid_name_token(s->token.val)) {
    errmsg = "Expecting tag name";
    goto fail;
  } else
     //tag
     tag_atom = JS_DupAtom(s->ctx,s->token.u.ident.atom);

  tag = JS_AtomToString(s->ctx,tag_atom);

  // load JSX function - driver of JSX expressions:
#if 1 // load it as a global function
  //emit_op(s, OP_get_var);
  //emit_atom(s, JS_ATOM_JSX);
  //emit_push_const(s,rt->jsx,0); - breaks bytecode serializer
  emit_op(s, OP_push_jsx);

#else // load it as a local/scope function - do we need that?
  emit_op(s, OP_scope_get_var);
  emit_atom(s, JS_ATOM_JSX);
  emit_u16(s, s->cur_func->scope_level);
#endif

  //      #0   #1   #2
  // JSX(tag, atts ,kids); where
  //  - atts - object {...}, can be empty
  //  - kids - array [...], can be empty
  
  char buf[ATOM_GET_STR_BUF_SIZE];
  const char* tag_chars = JS_AtomGetStr(s->ctx, buf, countof(buf), tag_atom);
  
  // check for tag name starting from capital letter 
  uint32_t res[3] = {0};
  lre_case_conv(res, tag_chars[0], 1);

  if (res[0] != tag_chars[0]) { // tag name starts from upper case.
    // ReactJS convention, if tag started from capital letter - it is either class or function
    // Fetch it here, so the tag in JSX call can be reference to class(constructor) or a function
    if (strchr(tag_chars,'-')) {
      char toeval[ATOM_GET_STR_BUF_SIZE] = { 0 };
      const char* src = tag_chars;
      char* dst = toeval;
      for (int n = 0;n < ATOM_GET_STR_BUF_SIZE - 1; ++n) {
        char c = *src++;
        if (c == 0) break;
        if (c == '-') c = '.';
        *dst++ = c;
      }
      *dst = 0;
      JSAtom aexpr = JS_NewAtom(s->ctx, toeval);
      JSValue vexpr = JS_AtomToString(s->ctx, aexpr);
      emit_op(s, OP_scope_get_var);
      emit_atom(s, JS_ATOM_eval);
      emit_u16(s, s->cur_func->scope_level);
      if (emit_push_const(s, vexpr, 1))
        goto fail;
      emit_op(s,OP_call1);
      JS_FreeAtom(s->ctx, aexpr);
      JS_FreeValue(s->ctx, vexpr);
    }
    else {
      emit_op(s, OP_scope_get_var);
      emit_atom(s, tag_atom);
      emit_u16(s, s->cur_func->scope_level);
    }
  }
  else {
    if (emit_push_const(s, tag, 0))
      goto fail;
  }
  
  // parse attributes

  if (!is_fragment) {
    if (next_web_token(s)) goto fail;
  }
  
  emit_op(s, OP_object);

  while (s->token.val != '>') {

    if (s->token.val == '@') { // translation marker, see: https://gitlab.com/sciter-engine/sciter-js-sdk/-/blob/main/docs/md/reactor/JSX-i18n.md#jsx-attribute-to-translate
      translate = 1;
      if (next_web_token(s)) goto fail;
      if (s->token.val == '>') {
        if (translation_id != JS_UNINITIALIZED) {
          JS_FreeValue(s->ctx, translation_id);
          translation_id = JS_UNINITIALIZED;
        }
        break;
      }
    }

    if (s->token.val == '/') {
      if (next_token(s))
        goto fail;
      //json_parse_expect(s, '>');
      if (s->token.val != '>') {
        errmsg = "expecting '>'";
        goto fail;
      }
      //goto GENERATE_KIDS;
      is_bodyless = 1;
      break;
    }

#if defined(CONFIG_JSX_SCITER) // HTML shortcuts used by Sciter
    if (s->token.val == '#') { // <div #some> ->  <div id="some">
      if (next_web_token(s)) goto fail;
      if (invalid_name_token(s->token.val)) {
        errmsg = "expecting identifier";
        goto fail;
      }
      attr_name = JS_NewAtom(s->ctx,"id");
      attr_value = JS_AtomToString(s->ctx, s->token.u.ident.atom);
      goto PUSH_ATTR_VALUE;
    }
    if (s->token.val == '|') { // <input|text> ->  <input type="text">
      if (next_web_token(s)) goto fail;
      if (invalid_name_token(s->token.val)) {
        errmsg = "expecting identifier";
        goto fail;
      }
      attr_name = JS_NewAtom(s->ctx, "type");
      attr_value = JS_AtomToString(s->ctx, s->token.u.ident.atom);
      goto PUSH_ATTR_VALUE;
    }
    if (s->token.val == '(') { // <input(foo)> ->  <input name="foo">
      if (next_web_token(s)) goto fail;
      if (invalid_name_token(s->token.val)) {
        errmsg = "expecting identifier";
        goto fail;
      }
      attr_name = JS_NewAtom(s->ctx, "name");
      attr_value = JS_AtomToString(s->ctx, s->token.u.ident.atom);
      if (next_token(s)) goto fail;
      if (s->token.val != ')') { 
        errmsg = "expecting identifier";
        goto fail;
      }
      goto PUSH_ATTR_VALUE;
    }
    if (s->token.val == '.') { // <div.some> ->  <div class="some">
      if (next_web_token(s)) goto fail;
      if (invalid_name_token(s->token.val)) {
        errmsg = "expecting identifier";
        goto fail;
      }
      char cls1[256];
      const char *name = JS_AtomGetStr(s->ctx, cls1, countof(cls1), s->token.u.ident.atom);
      if (strlen(class_buffer) + strlen(name) + 2 < countof(class_buffer)) {
        if(class_buffer[0]) strcat(class_buffer, " ");
        strcat(class_buffer, name);
      }
      if (next_web_token(s)) goto fail;
      continue;
    }
#endif
    /*if (s->token.val == '@') {
      if (next_web_token(s)) goto fail;
      if (invalid_name_token(s->token.val)) {
        errmsg = "expecting identifier";
        goto fail;
      }
      translation_id = JS_AtomToString(s->ctx, s->token.u.ident.atom);
      if (next_web_token(s)) goto fail;
      continue;
    }*/

    if (s->token.val == '{') // <a {atts}>foo</a>
    {
      if (next_token(s))
        goto fail;
      if (s->token.val == TOK_ELLIPSIS) {  // fancy <Foo {...obj} />
        if (next_token(s))                 // which is really just <Foo {obj} />  
          goto fail;
      }
      if (js_parse_assign_expr(s))
        goto fail;
      if (s->token.val != '}') {
        errmsg = "expecting '}'";
        goto fail;
      }
      emit_op(s, OP_null);  /* dummy excludeList */
      emit_op(s, OP_copy_data_properties);
      emit_u8(s, 2 | (1 << 2) | (0 << 5));
      emit_op(s, OP_drop); /* pop excludeList */
      emit_op(s, OP_drop); /* pop src object */

      if (next_web_token(s))
        goto fail;

      continue;
    } 
    else if (token_is_ident(s->token.val)) 
    {
      /* keywords and reserved words have a valid atom */
      attr_name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
      if (next_web_token(s))
        goto fail;
    }
    else {
      errmsg = "unexpected token in JSX expression";
      goto fail;
    }

    if (s->token.val == '=') {
      token_read = 0;
      if (next_web_token(s)) // eat `=`
        goto fail;
    }
    else {
      token_read = 1;
      if (translate) {
        if (translation_id != JS_UNINITIALIZED)
          JS_FreeValue(s->ctx, translation_id);
        translation_id = JS_AtomToString(s->ctx, attr_name);
        continue;
      }
      attr_value = JS_AtomToString(s->ctx, JS_ATOM_empty_string);
      goto PUSH_ATTR_VALUE;
    }

    if (s->token.val == TOK_STRING) {
      // i18n
      if (translate && JS_IsFunction(s->ctx,rt->jsx_translate_text)) {
        rt->jsx_translate_filename = s->filename;
        rt->jsx_translate_line_no = s->line_num;
        JSValue attrname = JS_AtomToString(s->ctx, attr_name);
        JSValue args[3] = { s->token.u.str.str, attrname, JS_NewInt32(s->ctx,0) };
        attr_value = JS_Call(s->ctx, rt->jsx_translate_text, JS_NULL, 3, args);
        rt->jsx_translate_filename = NULL;
        JS_FreeValue(s->ctx, attrname);
      }
      else 
        attr_value = JS_DupValue(s->ctx, s->token.u.str.str);
    PUSH_ATTR_VALUE:
      if (emit_push_const(s, attr_value, 0))
        goto fail;
      JS_FreeValue(s->ctx, attr_value);
      translate = 0;
    }
    else if (s->token.val == TOK_TEMPLATE) {
      if (js_parse_template(s, 0, NULL))
        goto fail;
      token_read = 1;
    }
    else if (s->token.val == '{')
    {
      if (next_token(s))
        goto fail;
      if (js_parse_assign_expr(s))
        goto fail;
      if (s->token.val != '}') {
        errmsg = "expecting '}'";
        goto fail;
      }
    }
    else if(s->token.val == TOK_NUMBER) {
      attr_value = JS_DupValue(s->ctx,s->token.u.num.val);
      goto PUSH_ATTR_VALUE;
    }
    else if (s->token.val == TOK_FALSE) {
      emit_op(s, OP_push_false);
    }
    else if (s->token.val == TOK_TRUE) {
      emit_op(s, OP_push_true);
    }
    else if (s->token.val == TOK_NULL) {
      emit_op(s, OP_null);
    }
    else {
      errmsg = "bad attribute value";
      goto fail;
    }

    set_object_name(s, attr_name);
    emit_op(s, OP_define_field);
    emit_atom(s, attr_name);
    JS_FreeAtom(s->ctx, attr_name);

    if (!token_read) {
      if (next_web_token(s))
        goto fail;
    }
  }

#if defined(CONFIG_JSX_SCITER) // HTML shortcuts used by Sciter
  if (class_buffer[0]) { // add remaining classes 
    attr_value = JS_NewString(s->ctx, class_buffer);
    int r = emit_push_const(s,attr_value, 0);
    JS_FreeValue(s->ctx, attr_value);
    if (r < 0) goto fail;
    attr_name = JS_NewAtom(s->ctx, "class");
    set_object_name(s, attr_name);
    emit_op(s, OP_define_field);
    emit_atom(s, attr_name);
    JS_FreeAtom(s->ctx, attr_name);
  }
#endif

  // parse content of the element

  if(translation_id != JS_UNINITIALIZED)
    translate = 1;

  if (!translate && JS_IsObject(rt->jsx_translate_tag_map) && tag_atom != JS_ATOM_NULL) {
    JSValue ttit = JS_GetProperty(s->ctx, rt->jsx_translate_tag_map, tag_atom);
    translate = JS_ToBoolFree(s->ctx, ttit) > 0;
  }

  char   translation_text[2048] = { 0 };
  size_t translation_text_length = 0;
  
  while(!is_bodyless) 
  {
    const uint8_t *p;
    p = s->last_ptr = s->buf_ptr;
    s->last_line_num = s->token.line_num;
    if (js_parse_jsx_text(s, p, &s->token, &p))
      goto fail;
    if (s->buf_ptr != p) {
      const uint8_t *start = s->buf_ptr;
      s->buf_ptr = p;
      if (is_non_space_run(start, p)) {
        JSValue str = JS_DupValue(s->ctx,s->token.u.str.str); //JS_NewStringLen(s->ctx, (const char*)start, p - start);
        if(str == JS_EXCEPTION)
          goto fail;
        // i18n
        if (translate) {
          if(kids_count == 0
            && JS_IsFunction(s->ctx, rt->jsx_translate_text)
            && s->buf_ptr[0] == '<' && s->buf_ptr[1] == '/')
          {
            rt->jsx_translate_filename = s->filename;
            rt->jsx_translate_line_no = s->line_num;
            JSValue args[3] = { str, tag, JS_NewInt32(s->ctx,1) };
            JSValue translated_str = JS_Call(s->ctx, rt->jsx_translate_text, JS_NULL, 3, args);
            rt->jsx_translate_filename = NULL;
            JS_FreeValue(s->ctx, str);
            str = translated_str;
          }
          else if(translation_id == JS_UNINITIALIZED) {
            size_t length = p - start;
            if (translation_text_length + length > countof(translation_text))
              length = countof(translation_text) - translation_text_length;
            strncat(translation_text + translation_text_length, (const char*)start, length);
            translation_text_length += length;
          }
        }

        if (emit_push_const(s,str, 1)) {
          JS_FreeValue(s->ctx, str);
          goto fail;
        }
        JS_FreeValue(s->ctx, str);
        ++kids_count;
      }
    }
    if (next_token(s))
      goto fail;

    if (s->token.val == '<') {
      if (*s->buf_ptr == '/') {
        if (next_token(s)) // skip '/'
          goto fail;
        if (next_web_token(s)) // get tail tag name
          goto fail;
        if (s->token.val == '>' && is_fragment) {
          goto AT_TAIL;
        }
        if (token_is_ident(s->token.val)) {  /* keywords and reserved words have a valid atom */
          if (s->token.u.ident.atom != tag_atom) {
            char atail[64];
            char ahead[64];
            snprintf(msg_buffer, countof(msg_buffer), "head <%s> and tail </%s> tags do not match",
              JS_AtomGetStr(s->ctx, ahead, countof(ahead), tag_atom),
              JS_AtomGetStr(s->ctx, atail, countof(atail), s->token.u.ident.atom));
            errmsg = msg_buffer;
            goto fail;
          }
          if (next_token(s))
             goto fail;
          AT_TAIL:
          if (s->token.val != '>') {
            errmsg = "expecting '>' in tail tag";
            goto fail;
          }
          break;
        }
      }
      else {
        if (translate 
          && translation_id == JS_UNINITIALIZED
          && translation_text_length + 2 < countof(translation_text))
        {
          strcat(translation_text + translation_text_length, "<>");
          translation_text_length += 2;
        }
        js_parse_jsx_expr(s, level + 1);
        ++kids_count;
      }
    }
    else if (s->token.val == '{') {
      if (next_token(s))
        goto fail;
      if (s->token.val == '}') {
        continue;
      }
      if (js_parse_assign_expr(s) < 0)
        goto fail;
      if (s->token.val != '}') {
        errmsg = "expected '}'";
        goto fail;
      }
      if (translate 
        && translation_id == JS_UNINITIALIZED
        && translation_text_length + 2 < countof(translation_text))
      {
        strcat(translation_text + translation_text_length, "{}");
        translation_text_length += 2;
      }
      ++kids_count;
    }
  }

//GENERATE_KIDS:
  emit_op(s, OP_array_from);
  emit_u16(s, kids_count);
  
  emit_op(s, OP_call);
  emit_u16(s, 3);

  if (level == 0) {
    if (next_token(s))
      goto fail;
  }
  
  if (translate && JS_IsFunction(s->ctx,rt->jsx_translate_node) && kids_count > 1) { // call JSXtranslateNode
    //JS_IsString(translation_id)
    if(emit_push_const(s, rt->jsx_translate_node, 0)) goto fail;
    emit_op(s, OP_swap);
    if(translation_id == JS_UNINITIALIZED)
      translation_id = JS_NewStringLen(s->ctx, translation_text, translation_text_length);
    if(emit_push_const(s, translation_id, 0)) goto fail;
    JSValue filename = JS_NewString(s->ctx, s->filename);
    if(emit_push_const(s, filename, 0)) goto fail;
    JS_FreeValue(s->ctx, filename);
    if(emit_push_const(s, JS_NewUint32(s->ctx, s->last_line_num), 1)) goto fail;
    emit_op(s, OP_call);
    emit_u16(s, 4);
  }

  JS_FreeValue(s->ctx, tag);
  JS_FreeAtom(s->ctx,  tag_atom);
  JS_FreeValue(s->ctx, translation_id);
  return 0;
fail:
  JS_FreeValue(s->ctx, tag);
  JS_FreeAtom(s->ctx,  tag_atom);
  JS_FreeValue(s->ctx, translation_id);
  return js_parse_error(s, errmsg);
}

static int js_parse_at_expression(JSParseState *s) {
  // caller parsed '@'
  if (s->token.val != TOK_STRING /* && s->token.val != TOK_TEMPLATE*/)
    return js_parse_error(s, "string literal expected");

  JSRuntime* rt = JS_GetRuntime(s->ctx);

  if (JS_IsFunction(s->ctx, rt->jsx_translate_text)) {
    rt->jsx_translate_filename = s->filename;
    rt->jsx_translate_line_no = s->line_num;
    JSValue args[3] = { s->token.u.str.str, JS_NULL, JS_NewInt32(s->ctx,2) };
    JSValue translated_str = JS_Call(s->ctx, rt->jsx_translate_text, JS_NULL, 3, args);
    rt->jsx_translate_filename = NULL;
    if (emit_push_const(s, translated_str, 1))
      return -1;
    JS_FreeValue(s->ctx, translated_str);
  }
  else {
    if (emit_push_const(s, s->token.u.str.str, 1))
      return -1;
  }
  if (next_token(s))
    return -1;
/*
  else if (s->token.val == TOK_TEMPLATE) {
    if (JS_IsFunction(s->ctx, rt->jsx_translate_template)) {
      const char* start = s->last_ptr + 1;
      char* end = start;
      for (; end < s->buf_end; ++end)
        if (*end == '`') break;
      // [start .. end) is the template source
      JSValue src = JS_NewStringLen(s->ctx,start,end - start);
      JSValue translated_fcn = JS_Call(s->ctx, rt->jsx_translate_template, JS_NULL, 1, &src);
      JS_FreeValue(s->ctx, src);
      if (emit_push_const(s, translated_fcn, 0))
        return -1;
      JS_FreeValue(s->ctx, translated_fcn);
      int argc = 0;
      if (js_parse_template(s, 1, &argc))
        return -1;
      emit_op(s, OP_call);
      emit_u16(s, argc);
    }
    else {
      //if (emit_push_const(s, s->token.u.str.str, 1))
      //  return -1;
      if (js_parse_template(s, 0, NULL))
        return -1;
    }
  } */

  return 0;

}

static JSValue jsx_get(JSContext *ctx, JSValueConst this_val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  return JS_DupValue(ctx,rt->jsx);
  //return ctx->jsx;
}

static JSValue jsx_set(JSContext *ctx, JSValueConst this_val, JSValueConst val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  JS_FreeValue(ctx,rt->jsx);
  rt->jsx = JS_UNDEFINED;
  if (JS_IsNull(val))
    ;
  else if (JS_IsObject(val) || JS_IsFunction(ctx, val)) {
    rt->jsx = JS_DupValue(ctx, val);
    return JS_UNDEFINED;
  }
  return JS_EXCEPTION;
}

void JS_SetJSX(JSContext *ctx, JSCFunction* pjsx) {
  JSRuntime* rt = JS_GetRuntime(ctx);
  JS_FreeValue(ctx, rt->jsx);
  rt->jsx = JS_NewCFunction(ctx,pjsx,"JSX",3);
}

void JS_SetEntityTranslator(JSRuntime* rt, JSTranslateEntityFunc* pft) {
  rt->jsx_translate_entity = pft;
}

static JSValue jsx_get_text_translator(JSContext *ctx, JSValueConst this_val) {
  JSRuntime* rt = JS_GetRuntime(ctx);
  return JS_DupValue(ctx, rt->jsx_translate_text);
}

static JSValue jsx_set_text_translator(JSContext *ctx, JSValueConst this_val, JSValueConst val) {
  JSRuntime* rt = JS_GetRuntime(ctx);
  JS_FreeValue(ctx, rt->jsx_translate_text);
  rt->jsx_translate_text = JS_UNDEFINED;
  if (JS_IsNull(val))
    ;
  else if (JS_IsObject(val) || JS_IsFunction(ctx, val)) {
    rt->jsx_translate_text = JS_DupValue(ctx, val);
    return JS_UNDEFINED;
  }
  return JS_EXCEPTION;
}

static JSValue jsx_get_node_translator(JSContext *ctx, JSValueConst this_val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  return JS_DupValue(ctx, rt->jsx_translate_node);
}

static JSValue jsx_set_node_translator(JSContext *ctx, JSValueConst this_val, JSValueConst val) {
  JSRuntime* rt = JS_GetRuntime(ctx);
  JS_FreeValue(ctx, rt->jsx_translate_node);
  rt->jsx_translate_node = JS_UNDEFINED;
  if (JS_IsNull(val))
    ;
  else if (JS_IsObject(val) || JS_IsFunction(ctx, val)) {
    rt->jsx_translate_node = JS_DupValue(ctx, val);
    return JS_UNDEFINED;
  }
  return JS_EXCEPTION;
}

/*static JSValue jsx_get_template_translator(JSContext *ctx, JSValueConst this_val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  return JS_DupValue(ctx, rt->jsx_translate_template);
}

static JSValue jsx_set_template_translator(JSContext *ctx, JSValueConst this_val, JSValueConst val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  JS_FreeValue(ctx, rt->jsx_translate_template);
  rt->jsx_translate_template = JS_UNDEFINED;
  if (JS_IsNull(val))
    ;
  else if (JS_IsFunction(ctx, val)) {
    rt->jsx_translate_template = JS_DupValue(ctx, val);
    return JS_UNDEFINED;
  }
  return JS_EXCEPTION;
}*/


static JSValue jsx_get_translator_tag_map(JSContext *ctx, JSValueConst this_val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  return JS_DupValue(ctx, rt->jsx_translate_tag_map);
}

static JSValue jsx_set_translator_tag_map(JSContext *ctx, JSValueConst this_val, JSValueConst val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  JS_FreeValue(ctx, rt->jsx_translate_tag_map);
  rt->jsx_translate_tag_map = JS_UNDEFINED;
  if (JS_IsNull(val))
    ;
  else if (JS_IsObject(val)) {
    rt->jsx_translate_tag_map = JS_DupValue(ctx, val);
    return JS_UNDEFINED;
  }
  return JS_EXCEPTION;
}

static JSValue jsx_get_translation_context(JSContext *ctx, JSValueConst this_val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  return JS_NewString(ctx,rt->jsx_translate_context.text);
}

static JSValue jsx_get_translation_filename(JSContext *ctx, JSValueConst this_val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  return JS_NewString(ctx, rt->jsx_translate_filename ? rt->jsx_translate_filename : "");
}

static JSValue jsx_get_translation_lineno(JSContext *ctx, JSValueConst this_val)
{
  JSRuntime* rt = JS_GetRuntime(ctx);
  return JS_NewUint32(ctx, rt->jsx_translate_line_no);
}



